From af80f3a9765df5a2b09e99dc43f6a8b9f2b52819 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Thu, 18 Mar 2021 17:53:37 -0700 Subject: [PATCH] ngl: implement atlas compaction This required finishing up the begin_frame/end_frame semantics for GskNglTextureLibraryw which was apparently overlooked. The driver was changed to provide more information to the library when beginning frames. We do not need to use end_frame so that was removed. The frame age is the same as GL (60) but I do wonder if that is based on seconds if we should be using something longer for situations where we have higher frame rates. Fixes #3771 --- gsk/ngl/gskngldriver.c | 53 +++++++++++++++++++--- gsk/ngl/gskngltexturelibrary.c | 64 +++++++++++++++++++++++---- gsk/ngl/gskngltexturelibraryprivate.h | 39 ++++++++++------ 3 files changed, 128 insertions(+), 28 deletions(-) diff --git a/gsk/ngl/gskngldriver.c b/gsk/ngl/gskngldriver.c index 93763dbb12..a4254c8da4 100644 --- a/gsk/ngl/gskngldriver.c +++ b/gsk/ngl/gskngldriver.c @@ -39,6 +39,7 @@ #include "gskngltexturepoolprivate.h" #define ATLAS_SIZE 512 +#define MAX_OLD_RATIO 0.5 typedef struct _GskNglTextureState { @@ -494,6 +495,37 @@ failure: return g_steal_pointer (&driver); } +static GPtrArray * +gsk_ngl_driver_compact_atlases (GskNglDriver *self) +{ + GPtrArray *removed = NULL; + + g_assert (GSK_IS_NGL_DRIVER (self)); + + for (guint i = self->atlases->len; i > 0; i--) + { + GskNglTextureAtlas *atlas = g_ptr_array_index (self->atlases, i - 1); + + if (gsk_ngl_texture_atlas_get_unused_ratio (atlas) > MAX_OLD_RATIO) + { + GSK_NOTE (GLYPH_CACHE, + g_message ("Dropping atlas %d (%g.2%% old)", i, + 100.0 * gsk_ngl_texture_atlas_get_unused_ratio (atlas))); + if (removed == NULL) + removed = g_ptr_array_new_with_free_func ((GDestroyNotify)gsk_ngl_texture_atlas_free); + g_ptr_array_add (removed, g_ptr_array_steal_index (self->atlases, i - 1)); + } + } + + GSK_NOTE (GLYPH_CACHE, { + static guint timestamp; + if (timestamp++ % 60 == 0) + g_message ("%d atlases", self->atlases->len); + }); + + return removed; +} + /** * gsk_ngl_driver_begin_frame: * @self: a #GskNglDriver @@ -510,6 +542,7 @@ gsk_ngl_driver_begin_frame (GskNglDriver *self, GskNglCommandQueue *command_queue) { gint64 last_frame_id; + GPtrArray *removed; g_return_if_fail (GSK_IS_NGL_DRIVER (self)); g_return_if_fail (GSK_IS_NGL_COMMAND_QUEUE (command_queue)); @@ -524,8 +557,18 @@ gsk_ngl_driver_begin_frame (GskNglDriver *self, gsk_ngl_command_queue_begin_frame (self->command_queue); - gsk_ngl_texture_library_begin_frame (GSK_NGL_TEXTURE_LIBRARY (self->icons)); - gsk_ngl_texture_library_begin_frame (GSK_NGL_TEXTURE_LIBRARY (self->glyphs)); + /* Compact atlases with too many freed pixels */ + removed = gsk_ngl_driver_compact_atlases (self); + + /* Mark unused pixel regions of the atlases */ + gsk_ngl_texture_library_begin_frame (GSK_NGL_TEXTURE_LIBRARY (self->icons), + self->current_frame_id, + removed); + gsk_ngl_texture_library_begin_frame (GSK_NGL_TEXTURE_LIBRARY (self->glyphs), + self->current_frame_id, + removed); + + /* Cleanup old shadows */ gsk_ngl_shadow_library_begin_frame (self->shadows); /* Remove all textures that are from a previous frame or are no @@ -534,6 +577,9 @@ gsk_ngl_driver_begin_frame (GskNglDriver *self, * we block on any resources while delivering our frames. */ gsk_ngl_driver_collect_unused_textures (self, last_frame_id - 1); + + /* Now free atlas textures */ + g_clear_pointer (&removed, g_ptr_array_unref); } /** @@ -553,9 +599,6 @@ gsk_ngl_driver_end_frame (GskNglDriver *self) gsk_ngl_command_queue_make_current (self->command_queue); gsk_ngl_command_queue_end_frame (self->command_queue); - gsk_ngl_texture_library_end_frame (GSK_NGL_TEXTURE_LIBRARY (self->icons)); - gsk_ngl_texture_library_end_frame (GSK_NGL_TEXTURE_LIBRARY (self->glyphs)); - self->in_frame = FALSE; } diff --git a/gsk/ngl/gskngltexturelibrary.c b/gsk/ngl/gskngltexturelibrary.c index dc9303f373..85449b3fa2 100644 --- a/gsk/ngl/gskngltexturelibrary.c +++ b/gsk/ngl/gskngltexturelibrary.c @@ -20,10 +20,14 @@ #include "config.h" +#include + #include "gsknglcommandqueueprivate.h" #include "gskngldriverprivate.h" #include "gskngltexturelibraryprivate.h" +#define MAX_FRAME_AGE 60 + G_DEFINE_ABSTRACT_TYPE (GskNglTextureLibrary, gsk_ngl_texture_library, G_TYPE_OBJECT) enum { @@ -130,21 +134,62 @@ gsk_ngl_texture_library_set_funcs (GskNglTextureLibrary *self, } void -gsk_ngl_texture_library_begin_frame (GskNglTextureLibrary *self) +gsk_ngl_texture_library_begin_frame (GskNglTextureLibrary *self, + gint64 frame_id, + GPtrArray *removed_atlases) { + GHashTableIter iter; + g_return_if_fail (GSK_IS_NGL_TEXTURE_LIBRARY (self)); if (GSK_NGL_TEXTURE_LIBRARY_GET_CLASS (self)->begin_frame) - GSK_NGL_TEXTURE_LIBRARY_GET_CLASS (self)->begin_frame (self); -} + GSK_NGL_TEXTURE_LIBRARY_GET_CLASS (self)->begin_frame (self, frame_id, removed_atlases); -void -gsk_ngl_texture_library_end_frame (GskNglTextureLibrary *self) -{ - g_return_if_fail (GSK_IS_NGL_TEXTURE_LIBRARY (self)); + if (removed_atlases != NULL) + { + GskNglTextureAtlasEntry *entry; + guint dropped = 0; + + g_hash_table_iter_init (&iter, self->hash_table); + while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&entry)) + { + if (entry->is_atlased) + { + for (guint i = 0; i < removed_atlases->len; i++) + { + GskNglTextureAtlas *atlas = g_ptr_array_index (removed_atlases, i); + + if (atlas == entry->atlas) + { + g_hash_table_iter_remove (&iter); + dropped++; + break; + } + } + } + } + + GSK_NOTE (GLYPH_CACHE, + if (dropped > 0) + g_message ("%s: Dropped %d icons", + G_OBJECT_TYPE_NAME (self), dropped)); + } - if (GSK_NGL_TEXTURE_LIBRARY_GET_CLASS (self)->end_frame) - GSK_NGL_TEXTURE_LIBRARY_GET_CLASS (self)->end_frame (self); + if (frame_id % MAX_FRAME_AGE == 0) + { + GskNglTextureAtlasEntry *entry; + + g_hash_table_iter_init (&iter, self->hash_table); + while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&entry)) + { + gsk_ngl_texture_atlas_entry_mark_unused (entry); + entry->accessed = FALSE; + } + + GSK_NOTE (GLYPH_CACHE, g_message ("%s: %d atlas items cached", + G_OBJECT_TYPE_NAME (self), + g_hash_table_size (self->hash_table))); + } } static GskNglTexture * @@ -252,6 +297,7 @@ gsk_ngl_texture_library_pack (GskNglTextureLibrary *self, entry = g_slice_alloc0 (valuelen); entry->n_pixels = width * height; entry->accessed = TRUE; + entry->used = TRUE; /* If our size is invisible then we just want an entry in the * cache for faster lookups, but do not actually spend any texture diff --git a/gsk/ngl/gskngltexturelibraryprivate.h b/gsk/ngl/gskngltexturelibraryprivate.h index 7e042036eb..05c2216ed7 100644 --- a/gsk/ngl/gskngltexturelibraryprivate.h +++ b/gsk/ngl/gskngltexturelibraryprivate.h @@ -99,8 +99,9 @@ typedef struct _GskNglTextureLibraryClass { GObjectClass parent_class; - void (*begin_frame) (GskNglTextureLibrary *library); - void (*end_frame) (GskNglTextureLibrary *library); + void (*begin_frame) (GskNglTextureLibrary *library, + gint64 frame_id, + GPtrArray *removed_atlases); } GskNglTextureLibraryClass; G_DEFINE_AUTOPTR_CLEANUP_FUNC (GskNglTextureLibrary, g_object_unref) @@ -111,8 +112,9 @@ void gsk_ngl_texture_library_set_funcs (GskNglTextureLibrary *self, GEqualFunc equal_func, GDestroyNotify key_destroy, GDestroyNotify value_destroy); -void gsk_ngl_texture_library_begin_frame (GskNglTextureLibrary *self); -void gsk_ngl_texture_library_end_frame (GskNglTextureLibrary *self); +void gsk_ngl_texture_library_begin_frame (GskNglTextureLibrary *self, + gint64 frame_id, + GPtrArray *removed_atlases); gpointer gsk_ngl_texture_library_pack (GskNglTextureLibrary *self, gpointer key, gsize valuelen, @@ -126,14 +128,29 @@ static inline void gsk_ngl_texture_atlas_mark_unused (GskNglTextureAtlas *self, int n_pixels) { + g_assert (n_pixels >= 0); + self->unused_pixels += n_pixels; } static inline void -gsk_ngl_texture_atlas_mark_used (GskNglTextureAtlas *self, - int n_pixels) +gsk_ngl_texture_atlas_entry_mark_used (GskNglTextureAtlasEntry *entry) +{ + if (entry->used == TRUE || entry->is_atlased == FALSE) + return; + + entry->atlas->unused_pixels -= entry->n_pixels; + entry->used = TRUE; +} + +static inline void +gsk_ngl_texture_atlas_entry_mark_unused (GskNglTextureAtlasEntry *entry) { - self->unused_pixels -= n_pixels; + if (entry->used == FALSE || entry->is_atlased == FALSE) + return; + + entry->atlas->unused_pixels += entry->n_pixels; + entry->used = FALSE; } static inline gboolean @@ -151,13 +168,7 @@ gsk_ngl_texture_library_lookup (GskNglTextureLibrary *self, if (entry != NULL) { - if (!entry->used && entry->is_atlased) - { - g_assert (entry->atlas != NULL); - gsk_ngl_texture_atlas_mark_used (entry->atlas, entry->n_pixels); - entry->used = TRUE; - } - + gsk_ngl_texture_atlas_entry_mark_used (entry); entry->accessed = TRUE; *out_entry = entry; return TRUE; -- 2.30.2